package vip.liux.gateway.filters.decrypt;

import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import vip.liux.gateway.consts.GlobalConstant;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 路由过滤器：拦截POST+JSON请求，进行参数校验、解密、验签、重写body等处理，并对响应数据加密签名。
 * 只应用于指定路由，适合部分服务需要加解密签名的场景。
 */
@Component
public class RsaSignPostJsonDecryptGatewayFilter implements GatewayFilter, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(RsaSignPostJsonDecryptGatewayFilter.class);

    // 业务相关常量
    private static final String APP_ID_PARAM_NAME = "appId"; // 应用ID参数名
    private static final String ENCRYPT_PARAM_NAME = "enc_param"; // 加密参数名
    private static final String SIGN_PARAM_NAME = "sign"; // 签名参数名
    private static final String TIMESTAMP_PARAM_NAME = "timestamp"; // 时间戳参数名
    private static final String NONCE_PARAM_NAME = "nonce"; // 随机数参数名

    // 支持透传参数列表（可配置/可扩展/可读取配置中心）
    private static final Set<String> PASS_THROUGH_PARAM_NAMES = Set.of("traceId", "sourceType", "bizTag");

    // 时间戳有效期5分钟
    private static final long TIMESTAMP_EXPIRE_MILLIS = TimeUnit.MINUTES.toMillis(5);

    // 编码常量
    private static final String CHARSET_UTF8 = CharEncoding.UTF_8;

    // URL解码工具（线程安全）
    private static final URLCodec URL_CODEC = new URLCodec();

    // 防重放，简单本地缓存（如需多实例/分布式建议接入Redis等分布式缓存）
    private final Map<String, Long> nonceCache = new ConcurrentHashMap<>();

    // 应用密钥仓库
    private final AppKeyRepository appKeyRepository;

    public RsaSignPostJsonDecryptGatewayFilter(AppKeyRepository appKeyRepository) {
        this.appKeyRepository = appKeyRepository;
    }

    private static String asString(Object obj) {
        return obj == null ? "" : String.valueOf(obj);
    }

    private static byte[] readBytesAndRelease(DataBuffer dataBuffer) {
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        dataBuffer.read(bytes);
        DataBufferUtils.release(dataBuffer);
        return bytes;
    }

    /**
     * 解析业务参数字符串为Map，支持标准表单key/value和单key等边界场景，支持多次同key出现（后一次生效）。
     */
    public static Map<String, String> parseParamString(final String paramStr) {
        Map<String, String> map = new HashMap<>();
        if (StringUtils.isBlank(paramStr)) return map;
        String[] pairs = paramStr.split("&");
        for (String pair : pairs) {
            int idx = pair.indexOf('=');
            if (idx > 0) {
                String key = pair.substring(0, idx);
                String value = pair.substring(idx + 1);
                try {
                    map.put(URL_CODEC.decode(key, CHARSET_UTF8), URL_CODEC.decode(value, CHARSET_UTF8));
                } catch (Exception e) {
                    map.put(key, value);
                }
            } else if (StringUtils.isNotBlank(pair)) {
                try {
                    map.put(URL_CODEC.decode(pair, CHARSET_UTF8), "");
                } catch (Exception e) {
                    map.put(pair, "");
                }
            }
        }
        return map;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 只处理POST+JSON，不处理其他请求类型
        if (!isJsonPostRequest(request)) {
            logger.debug("非POST+JSON请求直接放行, Method={}, ContentType={}", request.getMethod(), request.getHeaders().getContentType());
            return wrapResponseBody(exchange, chain, null);
        }

        logger.debug("拦截POST+JSON: {}", request.getURI());
        return DataBufferUtils.join(request.getBody())
                .flatMap(dataBuffer -> processRequest(exchange, chain, dataBuffer));
    }

    private boolean isJsonPostRequest(ServerHttpRequest request) {
        return request.getMethod() == HttpMethod.POST &&
                MediaType.APPLICATION_JSON.isCompatibleWith(request.getHeaders().getContentType());
    }

    private Mono<Void> processRequest(ServerWebExchange exchange, GatewayFilterChain chain, DataBuffer dataBuffer) {
        byte[] bodyBytes = readBytesAndRelease(dataBuffer);
        String bodyStr = new String(bodyBytes, StandardCharsets.UTF_8);

        Map<String, Object> bodyMap;
        try {
            bodyMap = GlobalConstant.readValue(bodyStr, new TypeReference<>() {
            });
        } catch (Exception e) {
            logger.warn("请求体非JSON格式，解析失败: {}", e.getMessage());
            return writeErrorResponse(exchange, HttpStatus.BAD_REQUEST, ErrorCode.MISSING_PARAM, "请求体不是合法JSON");
        }

        // 透传参数收集
        Map<String, Object> passThroughParams = new HashMap<>();
        for (String passKey : PASS_THROUGH_PARAM_NAMES) {
            if (bodyMap.containsKey(passKey)) {
                passThroughParams.put(passKey, bodyMap.get(passKey));
            }
        }

        // 校验必须参数
        if (!validateParams(bodyMap)) {
            logger.warn("请求缺少必须参数，内容: {}", bodyStr);
            return writeErrorResponse(exchange, HttpStatus.BAD_REQUEST, ErrorCode.MISSING_PARAM, null);
        }

        String appId = asString(bodyMap.get(APP_ID_PARAM_NAME));
        String encParam = asString(bodyMap.get(ENCRYPT_PARAM_NAME));
        String sign = asString(bodyMap.get(SIGN_PARAM_NAME));
        String timestamp = asString(bodyMap.get(TIMESTAMP_PARAM_NAME));
        String nonce = asString(bodyMap.get(NONCE_PARAM_NAME));

        // 校验时间戳合法性与防重放
        if (!validateTimestampAndNonce(appId, nonce, timestamp)) {
            logger.warn("重放攻击/时钟漂移/请求超时, appId={}, nonce={}, timestamp={}", appId, nonce, timestamp);
            return writeErrorResponse(exchange, HttpStatus.UNAUTHORIZED, ErrorCode.REPLAY_OR_TIMEOUT, null);
        }

        // 查询密钥配置，若无配置直接返回401
        return appKeyRepository.getKeyPairConfig(appId)
                .flatMap(keyPairConfig -> {
                    // 解密enc_param，失败返回400
                    String decryptedParamString;
                    try {
                        decryptedParamString = CryptoUtil.rsaDecrypt(encParam, keyPairConfig.privateKey());
                        logger.debug("enc_param解密后: {}", decryptedParamString);
                        if (decryptedParamString.length() > 16 * 1024) {
                            logger.warn("业务参数长度异常 appId={}, 长度={}", appId, decryptedParamString.length());
                            return writeErrorResponse(exchange, HttpStatus.BAD_REQUEST, ErrorCode.DECRYPT_FAIL, "业务参数过长");
                        }
                    } catch (Exception e) {
                        return writeErrorResponse(exchange, HttpStatus.BAD_REQUEST, ErrorCode.DECRYPT_FAIL, null);
                    }

                    // 解析业务参数字符串为Map
                    Map<String, String> decryptedParams = parseParamString(decryptedParamString);

                    // 构造签名原文，包含业务参数+timestamp+nonce
                    SortedMap<String, String> signMap = new TreeMap<>(decryptedParams);
                    signMap.put(TIMESTAMP_PARAM_NAME, timestamp);
                    signMap.put(NONCE_PARAM_NAME, nonce);

                    String signRaw = buildSignRaw(signMap);

                    // HMAC签名比对
                    String serverSign = CryptoUtil.hmacSha256(signRaw, keyPairConfig.signSecret());
                    if (!serverSign.equalsIgnoreCase(sign)) {
                        logger.warn("签名校验失败 appId={}, signRaw={}, clientSign={}, serverSign={}", appId, signRaw, sign, serverSign);
                        return writeErrorResponse(exchange, HttpStatus.UNAUTHORIZED, ErrorCode.SIGN_FAIL, null);
                    }

                    // 重写body，并放入timestamp/nonce，防止后端缺参数
                    Map<String, Object> newBodyMap = new LinkedHashMap<>(decryptedParams);
                    newBodyMap.put(TIMESTAMP_PARAM_NAME, timestamp);
                    newBodyMap.put(NONCE_PARAM_NAME, nonce);
                    // 透传参数合入（不覆盖业务参数）
                    passThroughParams.forEach(newBodyMap::putIfAbsent);

                    String newBody = GlobalConstant.writeValueAsString(newBodyMap);
                    byte[] newBodyBytes = newBody.getBytes(StandardCharsets.UTF_8);

                    DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
                    Flux<DataBuffer> newBodyFlux = Flux.defer(() -> {
                        DataBuffer buffer = bufferFactory.wrap(newBodyBytes);
                        return Mono.just(buffer);
                    });

                    ServerWebExchange newExchange = decorateExchangeWithBody(exchange, newBodyFlux, newBodyBytes.length);

                    // 继续过滤响应（加密签名），body清理
                    return wrapResponseBody(
                            newExchange,
                            chain, appId
                    ).doFinally(signal -> {
                        Arrays.fill(bodyBytes, (byte) 0);
                        Arrays.fill(newBodyBytes, (byte) 0);
                    });
                })
                .switchIfEmpty(Mono.defer(() ->
                        writeErrorResponse(exchange, HttpStatus.UNAUTHORIZED, ErrorCode.NO_KEY_CONFIG, null)
                ));
    }

    private boolean validateParams(Map<String, Object> bodyMap) {
        return StringUtils.isNotBlank(asString(bodyMap.get(APP_ID_PARAM_NAME)))
                && StringUtils.isNotBlank(asString(bodyMap.get(ENCRYPT_PARAM_NAME)))
                && StringUtils.isNotBlank(asString(bodyMap.get(SIGN_PARAM_NAME)))
                && StringUtils.isNotBlank(asString(bodyMap.get(TIMESTAMP_PARAM_NAME)))
                && StringUtils.isNotBlank(asString(bodyMap.get(NONCE_PARAM_NAME)));
    }

    private String buildSignRaw(SortedMap<String, String> signMap) {
        StringBuilder signRaw = new StringBuilder();
        for (Map.Entry<String, String> entry : signMap.entrySet()) {
            if (signRaw.length() > 0) signRaw.append('&');
            signRaw.append(entry.getKey()).append('=').append(entry.getValue());
        }
        return signRaw.toString();
    }

    /**
     * 包装响应体，对响应数据加密签名（带appId）
     * 仅加密application/json响应。其余类型透传。
     */
    private Mono<Void> wrapResponseBody(ServerWebExchange exchange, GatewayFilterChain chain, String appIdIfKnown) {
        String appId = appIdIfKnown != null ? appIdIfKnown : exchange.getRequest().getHeaders().getFirst(APP_ID_PARAM_NAME);

        if (StringUtils.isBlank(appId)) {
            return chain.filter(exchange);
        }

        return appKeyRepository.getKeyPairConfig(appId)
                .flatMap(keyPairConfig -> {
                    ServerHttpResponseDecorator decorated = new ServerHttpResponseDecorator(exchange.getResponse()) {
                        @Override
                        public Mono<Void> writeWith(org.reactivestreams.Publisher<? extends DataBuffer> body) {
                            HttpHeaders headers = getDelegate().getHeaders();
                            MediaType contentType = headers.getContentType();
                            // 只对JSON响应加密签名，其余类型直接返回
                            if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
                                return super.writeWith(body);
                            }
                            return DataBufferUtils.join(Flux.from(body))
                                    .flatMap(dataBuffer -> processResponseBody(dataBuffer, keyPairConfig, exchange));
                        }
                    };
                    return chain.filter(exchange.mutate().response(decorated).build());
                })
                .switchIfEmpty(chain.filter(exchange));
    }

    /**
     * 处理响应体加密与签名，返回标准结构，异常时返回标准错误结构
     */
    private Mono<Void> processResponseBody(DataBuffer dataBuffer, KeyPairConfig keyPairConfig, ServerWebExchange exchange) {
        byte[] content = readBytesAndRelease(dataBuffer);
        String responseBody = new String(content, StandardCharsets.UTF_8);

        if (StringUtils.isBlank(responseBody)) {
            logger.debug("响应体为空，直接透传");
            return writeJsonResponse(exchange, "", HttpStatus.OK);
        }

        try {
            String encrypted = CryptoUtil.rsaEncrypt(responseBody, keyPairConfig.externalPublicKey());
            String signature = CryptoUtil.rsaSign(encrypted, keyPairConfig.externalPrivateKey());
            ApiEncryptedResponse apiEncryptedResponse = new ApiEncryptedResponse(encrypted, signature, Instant.now().toEpochMilli());
            return writeJsonResponse(exchange, apiEncryptedResponse, HttpStatus.OK);
        } catch (Exception e) {
            logger.error("响应加密或签名失败，返回业务明文", e);
            ApiErrorResponse err = new ApiErrorResponse(ErrorCode.RESPONSE_ENCRYPT_FAIL);
            return writeJsonResponse(exchange, err, HttpStatus.OK);
        }
    }

    private Mono<Void> writeJsonResponse(ServerWebExchange exchange, Object body, HttpStatus httpStatus) {
        String json = GlobalConstant.writeValueAsString(body);
        exchange.getResponse().setStatusCode(httpStatus);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }

    /**
     * 用于重写请求体，真正用 newBody 作为新的请求体。
     */
    private ServerWebExchange decorateExchangeWithBody(ServerWebExchange exchange, Flux<DataBuffer> newBody, int length) {
        ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public Flux<DataBuffer> getBody() {
                return newBody;
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(super.getHeaders());
                headers.setContentLength(length);
                return headers;
            }
        };
        return exchange.mutate().request(decorator).build();
    }

    @Override
    public int getOrder() {
        return -100;
    }

    /**
     * 时间戳窗口校验 + nonce防重放。防止时钟漂移、请求重放、暴力攻击。
     */
    private boolean validateTimestampAndNonce(String appId, String nonce, String timestampStr) {
        try {
            long nowMillis = System.currentTimeMillis();
            long reqMillis = Long.parseLong(timestampStr);
            if (Math.abs(nowMillis - reqMillis) > TIMESTAMP_EXPIRE_MILLIS) {
                return false;
            }
            String key = appId + ":" + nonce + ":" + timestampStr;
            nonceCache.entrySet().removeIf(e -> nowMillis - e.getValue() > TIMESTAMP_EXPIRE_MILLIS);
            if (nonceCache.containsKey(key)) return false;
            nonceCache.put(key, nowMillis);
            return true;
        } catch (Exception e) {
            logger.warn("validateTimestampAndNonce异常", e);
            return false;
        }
    }

    private Mono<Void> writeErrorResponse(ServerWebExchange exchange, HttpStatus status, ErrorCode errorCode, String customMsg) {
        ApiErrorResponse err = (customMsg == null) ? new ApiErrorResponse(errorCode) : new ApiErrorResponse(errorCode, customMsg);
        return writeJsonResponse(exchange, err, status);
    }
}